Import trans

Class PyTranslator Extends CTranslator

	Field numTabs = 0
	Field checklastline = 0
	
	Method TransValue$( ty:Type,value$ )
		If value
			If BoolType( ty ) Return "True"
			If NumericType( ty ) Return value
			If StringType( ty ) Return Enquote( value )
		Else
			If BoolType( ty ) Return "False"
			If NumericType( ty ) Return "0"
			If StringType( ty ) Return "~q~q"
			If ArrayType( ty ) Return "[]"
			If ObjectType( ty ) Return "None"
		Endif
		InternalErr
	End
	
	Method TransDecl$( decl:Decl )
		Local vdecl:=ValDecl( decl )
		If vdecl Return decl.munged
		InternalErr
	End
	
	Method TransArgs$( args:Expr[] )
		Local t$
		For Local arg:=Eachin args
			If t t+=","
			t+=arg.Trans()
		Next
		Return Bra( t )
	End
	
	'***** Utility *****
	
	Method CreateLocal$( expr:Expr )
		Local tmp:=New LocalDecl( "",0,expr.exprType,expr )
		MungDecl tmp
		Emit TransLocalDecl( tmp.munged,expr )
		Return tmp.munged
	End
	
	Method TransStmtExpr$( expr:StmtExpr )
		Local t$=expr.stmt.Trans()
		If t Emit t
		Return expr.expr.Trans()
	End
	
	Method TransAssignStmt$( stmt:AssignStmt )
		If Not stmt.rhs
			Return stmt.lhs.Trans()
		Endif
		
		If stmt.tmp1
			MungDecl stmt.tmp1
			Emit TransLocalDecl( stmt.tmp1.munged,stmt.tmp1.init )
		Endif
		If stmt.tmp2
			MungDecl stmt.tmp2
			Emit TransLocalDecl( stmt.tmp2.munged,stmt.tmp2.init )
		Endif
		
		Return TransAssignStmt2( stmt )
	End
	
	Method TransIfStmt$( stmt:IfStmt )
		
		If ConstExpr( stmt.expr )
			If ConstExpr( stmt.expr ).value
				DecTab()
				If EmitBlock( stmt.thenBlock ) unreachable=True
				IncTab()
			Else If Not stmt.elseBlock.stmts.IsEmpty()
				DecTab()
				If EmitBlock( stmt.elseBlock ) unreachable=True
				IncTab()
			Endif
		Else If Not stmt.elseBlock.stmts.IsEmpty()
			Emit "if "+Bra( stmt.expr.Trans() )+":"
			CheckPassBlock(stmt.thenBlock)
			
			Local unr=EmitBlock( stmt.thenBlock )
			Emit "else:"
			CheckPassBlock(stmt.elseBlock)
			Local unr2=EmitBlock( stmt.elseBlock )
			If unr And unr2 unreachable=True
		Else
			Emit "if "+Bra( stmt.expr.Trans() )+":"
			CheckPassBlock(stmt.thenBlock)
			Local unr=EmitBlock( stmt.thenBlock )
		Endif
	End
	
	Method TransWhileStmt$( stmt:WhileStmt )
		Local nbroken=broken
		
		Emit "while"+Bra( stmt.expr.Trans() )+":"
		CheckPassBlock(stmt.block)
		Local unr=EmitBlock( stmt.block )
		
		If broken=nbroken And ConstExpr( stmt.expr ) And ConstExpr( stmt.expr ).value unreachable=True
		broken=nbroken
	End
	
	Method TransRepeatStmt$( stmt:RepeatStmt )
		Local nbroken=broken

		Emit "while True:"
		Local unr=EmitBlock( stmt.block )
		IncTab()
		Emit "if "+Bra( stmt.expr.Trans() )+":"
		IncTab()
		Emit "break"
		DecTab()
		DecTab()
		
		If broken=nbroken And ConstExpr( stmt.expr ) And Not ConstExpr( stmt.expr ).value unreachable=True
		broken=nbroken
	End	
	
	Method TransForStmt$( stmt:ForStmt )
		Local nbroken=broken
		
		Local init$=stmt.init.Trans()
		Local expr$=stmt.expr.Trans()
		Local incr$=stmt.incr.Trans()

		Emit init
		Emit "while "+Bra(expr)+":"	
		Local unr=EmitBlock( stmt.block )
		IncTab()
		Emit incr
		DecTab()
		
		If broken=nbroken And ConstExpr( stmt.expr ) And ConstExpr( stmt.expr ).value unreachable=True
		broken=nbroken
	End
	
	Method CheckPassBlock( block:BlockDecl)
		If block.stmts.Count() = 0
			IncTab()
			Emit "pass"
			DecTab()
		EndIf
	End
	
	Method TransLocalDecl$( munged$,init:Expr )
		Return munged+"="+init.Trans()
	End
	
	Method EmitEnter( func$ )
		Emit "bb_std_lang.pushErr()"
	End
	
	Method EmitSetErr( info$ )
		Emit "bb_std_lang.errInfo=~q"+info.Replace( "\","/" )+"~q"
	End
	
	Method EmitLeave()
		Emit "bb_std_lang.popErr()"
	End
	
	'***** Declarations *****
	
	Method TransStatic$( decl:Decl )
		If decl.IsExtern()
			Return decl.munged
		Else If _env And decl.scope And decl.scope=_env.ClassScope()
			Return decl.scope.munged+"."+decl.munged
		Else If ClassDecl( decl.scope )
			Return decl.scope.munged+"."+decl.munged
		Else If ModuleDecl( decl.scope )
			Return decl.scope.munged+"."+decl.munged
		Endif
		InternalErr
	End

	Method TransGlobal$( decl:GlobalDecl )
		Return TransStatic( decl )
	End
	
	Method TransField$( decl:FieldDecl,lhs:Expr )
		If lhs Return TransSubExpr( lhs )+"."+decl.munged
		Return "self."+decl.munged
	End
		
	Method TransFunc$( decl:FuncDecl,args:Expr[],lhs:Expr )
		If decl.IsMethod()
			If lhs Return TransSubExpr( lhs )+"."+decl.munged+TransArgs( args )
			Return "self."+decl.munged+TransArgs( args )
		Endif
		Return TransStatic( decl )+TransArgs( args )
	End
	
	Method TransSuperFunc$( decl:FuncDecl,args:Expr[] )
		Local cdecl:=decl.ClassScope()
		
		Return "super(type(self), self)."+decl.munged+TransArgs( args )
	End

	'***** Expressions *****
	
	Method TransConstExpr$( expr:ConstExpr )
		Return TransValue( expr.exprType,expr.value )
	End
	
	Method TransNewObjectExpr$( expr:NewObjectExpr )
		Local t$=expr.classDecl.munged+"()"
		If expr.ctor t+="."+expr.ctor.munged+TransArgs( expr.args )
		Return t
	End
	
	Method TransNewArrayExpr$( expr:NewArrayExpr )
		Local texpr$=expr.expr.Trans()
		Local elemTy:=ArrayType( expr.exprType ).elemType
		'
		If StringType( elemTy ) Return "bb_std_lang.stringArray"+Bra(texpr)
		'		
		Local t$="[None]*"+texpr
		
		If texpr = ""
			t = "[]"
		EndIf
		
		Return t
	End
		
	Method TransSelfExpr$( expr:SelfExpr )
		Return "self"
	End
	
	Method TransCastExpr$( expr:CastExpr )
		Local dst:=expr.exprType
		Local src:=expr.expr.exprType
		Local uexpr$=expr.expr.Trans()
		Local texpr$=Bra( uexpr )
		
		If BoolType( dst )
			If BoolType( src ) Return texpr
			If IntType( src ) Return Bra( texpr+"!=0" )
			If FloatType( src ) Return Bra( texpr+"!=0.0" )
			If StringType( src ) Return Bra( "len"+texpr+"!=0" )
			If ArrayType( src ) Return Bra( "len"+texpr+"!=0" )
			If ObjectType( src ) Return Bra( texpr+"!=None" )
		Else If IntType( dst )
			If BoolType( src ) Return Bra( texpr+">0" )
			If IntType( src ) Return texpr
			If FloatType( src ) Return "int"+texpr
			If StringType( src ) Return "int"+texpr
		Else If FloatType( dst )
			If IntType( src ) Return "float"+texpr
			If FloatType( src ) Return texpr
			If StringType( src ) Return "float"+texpr
		Else If StringType( dst )
			If IntType( src ) Return "str"+texpr
			If FloatType( src ) Return "str"+texpr
			If StringType( src ) Return texpr
		Else If ObjectType( dst ) And ObjectType( src )
			If src.GetClass().ExtendsClass( dst.GetClass() )
				'upcast
				Return texpr
			Else 
				'downcast
				Local tmp:=New LocalDecl( "",0,src,Null )
				MungDecl tmp
				Emit tmp.munged+"="+expr.expr.Trans()
				Return tmp.munged
			Endif
		Endif
		Err "PY translator can't convert "+src.ToString()+" to "+dst.ToString()
	End
	
	Method TransUnaryExpr$( expr:UnaryExpr )
		Local pri=ExprPri( expr )
		Local t_expr$=TransSubExpr( expr.expr,pri )
		Return TransUnaryOp( expr.op )+t_expr
	End
	
	Method TransBinaryExpr$( expr:BinaryExpr )
		If BinaryCompareExpr( expr ) And StringType( expr.lhs.exprType ) And StringType( expr.rhs.exprType )
			Return Bra( "bb_std_lang.stringCompare("+TransSubExpr( expr.lhs )+", "+expr.rhs.Trans()+")"+TransBinaryOp( expr.op,"" )+"0" )
		Endif
		Local pri=ExprPri( expr )
		Local t_lhs$=TransSubExpr( expr.lhs,pri )
		Local t_rhs$=TransSubExpr( expr.rhs,pri-1 )
		Return t_lhs+TransBinaryOp( expr.op,t_rhs )+t_rhs

	End
	
	Method TransUnaryOp$( op$ )
		If op = "not" Return " not "
		Return op
		InternalErr
	End
	
	Method TransBinaryOp$( op$,rhs$ )
		Select op
		Case "+","-"
			If rhs.StartsWith( op ) Return op+" "
			Return op
		Case "*","/" Return op
		Case "shl" Return "<<"
		Case "shr" Return ">>"
		Case "mod" Return " % "
		Case "and" Return " and "
		Case "or" Return " or "
		Case "=" Return "=="
		Case "<>" Return "!="
		Case "<","<=",">",">=" Return op
		Case "&","|" Return op
		Case "~~" Return "^"
		End Select
		InternalErr
	End
	
	Method TransIndexExpr$( expr:IndexExpr )
		Local t_expr:=TransSubExpr( expr.expr )
		Local t_index:=expr.index.Trans()
		If StringType( expr.expr.exprType ) Return "ord("+t_expr+"["+t_index+"])"
		Return t_expr+"["+t_index+"]"
	End
	
	Method TransSliceExpr$( expr:SliceExpr )
		Local t_expr:=expr.expr.Trans()
		Local t_args:="0"
		If expr.from t_args=expr.from.Trans()
			
		If expr.term
			t_args+=","+expr.term.Trans()
			Return "(bb_std_lang.sliceFT("+t_expr+","+t_args+"))"
		EndIf
		Return "(bb_std_lang.slice("+t_expr+","+t_args+"))"
	End

	Method TransArrayExpr$( expr:ArrayExpr )
		Local t$
		For Local elem:=Eachin expr.exprs
			If t t+=","
			t+=elem.Trans()
		Next
		Return "("+t+")"
	End
	
	Method Bra$( str$ )

		Return "("+str+")"
	End
	
	Method TransIntrinsicExpr$( decl:Decl,expr:Expr,args:Expr[] )
		Local texpr$,arg0$,arg1$,arg2$
		
		If expr texpr=TransSubExpr( expr )
		
		If args.Length>0 And args[0] arg0=args[0].Trans()
		If args.Length>1 And args[1] arg1=args[1].Trans()
		If args.Length>2 And args[2] arg2=args[2].Trans()
		
		Local id$=decl.munged[1..]
		Local id2$=id[..1].ToUpper()+id[1..]

		Select id

		'global functions
		Case "print" Return "print"+Bra( arg0 )
		Case "error" Return "bb_std_lang.error"+Bra( arg0 )

		'string/array methods
		Case "length"
			Return "len"+Bra( texpr )
		
		'array methods
		Case "resize" 
			Local fn$="resizeArray"
			Local ty:=ArrayType( expr.exprType ).elemType
			If StringType( ty )
				fn="resizeArray"
			Else If ArrayType( ty )
				fn="resizeArray"
			Endif
			Return "bb_std_lang."+fn+Bra( texpr+","+arg0 )

		'string methods
		Case "compare" Return "bb_std_lang.stringCompare"+Bra( texpr+","+arg0 )
		Case "find" Return texpr+".find"+Bra( arg0+","+arg1 )
		Case "findlast" Return texpr+".rfind"+Bra( arg0 )
		Case "findlast2" Return texpr+".rfind"+Bra( arg0+","+arg1 )
		Case "trim" Return texpr+".strip()"
		Case "join" Return "str.join"+Bra( texpr+","+arg0 )
		Case "split" Return "bb_std_lang.split"+Bra( texpr+","+arg0 )
		Case "replace" Return texpr+".replace"+Bra( arg0+","+arg1 )
		Case "tolower" Return texpr+".lower()"
		Case "toupper" Return texpr+".upper()"
		Case "contains" Return Bra( arg0+" in "+texpr )
		Case "startswith" Return texpr+".startswith"+Bra( arg0 )
		Case "endswith" Return texpr+".endswith"+Bra( arg0 )

		'string functions
		Case "fromchar" Return "bb_std_lang.fromChars"+Bra( arg0 )
		Case "fromchars" Return "bb_std_lang.fromChars"+Bra( arg0 )

		'trig functions - degrees
		Case "sin","cos","tan" Return "math."+id+Bra( Bra(arg0)+"*bb_std_lang.D2R" )
		Case "asin","acos","atan" Return "math."+id+Bra(arg0)+"*bb_std_lang.R2D"
		Case "atan2" Return "math."+id+Bra(arg0+","+arg1)+"*bb_std_lang.R2D"

		'trig functions - radians
		Case "sinr","cosr","tanr" Return "math."+id[..-1]+Bra( arg0 )
		Case "asinr","acosr","atanr" Return "math."+id[..-1]+Bra( arg0 )
		Case "atan2r" Return "math."+id[..-1]+Bra( arg0+","+arg1 )

		'misc math functions
		Case "sqrt","floor","log","exp","ceil" Return "math."+id+Bra(arg0)
		'Case "ceil" Return "math.ceil"+Bra(arg0)
		Case "pow" Return "math."+id+Bra( arg0+","+arg1 )

		End Select
		
		InternalErr
	End

	'***** Statements *****
	
	Method TransMemberVarExpr$( expr:MemberVarExpr )
		Local decl:=VarDecl( expr.decl )
		
		If decl.munged.StartsWith( "$" ) Return TransIntrinsicExpr( decl,expr.expr,[] )
		
		If FieldDecl( decl ) Return TransField( FieldDecl( decl ),expr.expr )

		InternalErr
	End
	
	'***** Declarations *****

	Method EmitFuncDecl( decl:FuncDecl)
		BeginLocalScope
		
		Local args$
		For Local arg:=Eachin decl.argDecls
			MungDecl arg
			If args args+=","
			
			If arg.ident = "py|self"
				args += "self"
			Else
				args+=arg.munged
			EndIf
			
		Next
		
		Local t$="def "+decl.munged+Bra( args )
		
		Emit ""
		If decl.IsStatic()
			Emit "@staticmethod"
		Endif
		

		Emit t+":"
		EmitBlock decl
		
		EndLocalScope
	End
	
	Method EmitClassDecl( classDecl:ClassDecl )
	
		Local classid$=classDecl.munged
		Local superid$=classDecl.superClass.munged
		
		If classDecl.IsInterface() 
		
		#rem
			Local bases$
			For Local iface:=Eachin classDecl.implments
				If bases bases+="," Else bases=" : "
				bases+=iface.munged
			Next
			
			Emit "interface "+classid+bases+"{"
			For Local decl:=Eachin classDecl.Semanted
				Local fdecl:=FuncDecl( decl )
				If Not fdecl Continue
				EmitFuncDecl fdecl
			Next
			Emit "}"
			
		#end
		
			Return
		Endif
	
		If superid = "Object"
			superid = "object"
		EndIf
		
		Local bases$=" ( "+superid
		'For Local iface:=Eachin classDecl.implments
	'		bases+=","+iface.munged
'		Next
		bases+=" ):"
		
		Local q$ = ""
		
		'If classDecl.IsAbstract() q+="abstract "
		
		Emit q+"class "+classid+bases
		
		IncTab()
		For Local decl:=Eachin classDecl.Semanted
			Local tdecl:=FieldDecl( decl )
			If tdecl
				Emit TransDecl( tdecl )+"=None"
				Continue
			Endif
		End
		Emit ""
		Emit "def __init__(self):"
		IncTab()
		
		If superid And superid <> "object"
			Emit superid+".__init__(self)"
		EndIf
		
		For Local decl:=Eachin classDecl.Semanted
			Local tdecl:=FieldDecl( decl )
			If tdecl
				Emit "self."+TransDecl( tdecl )+"="+tdecl.init.Trans()
				Continue
			Endif
		End
		Emit "pass"
		Emit ""
		DecTab()
		
		For Local decl:=Eachin classDecl.Semanted
			
			Local fdecl:=FuncDecl( decl )
			If fdecl
				If Not fdecl.IsStatic()
					fdecl.argDecls = ArgInsert(fdecl.argDecls, New ArgDecl("py|self", 0, New StringType, Null))
				EndIf
				EmitFuncDecl(fdecl)
				Continue
			Endif
			
			Local gdecl:=GlobalDecl( decl )
			If gdecl
				Emit TransDecl( gdecl )+"="+gdecl.init.Trans()
				Continue
			Endif
		Next
		
		Emit "pass"
		Emit ""
		DecTab()
		
	End
	
	Method TransApp$( app:AppDecl )
		
		app.mainModule.munged="bb_"
		app.mainFunc.munged="bbMain"
		
		For Local decl:=Eachin app.imported.Values
			MungDecl decl
		Next

		For Local decl:=Eachin app.Semanted

			MungDecl decl

			Local cdecl:=ClassDecl( decl )
			If Not cdecl Continue
			
			For Local decl:=Eachin cdecl.Semanted
			
				If FuncDecl( decl ) And FuncDecl( decl ).IsCtor()
					decl.ident=cdecl.ident+"_"+decl.ident
				Endif
				
				MungDecl decl
			Next
		Next
		
		'classes		
		For Local decl:=Eachin app.Semanted
			
			Local cdecl:=ClassDecl( decl )
			If cdecl
				EmitClassDecl cdecl
				Continue
			Endif
		Next
		
		'Translate globals
		For Local mdecl:=Eachin app.imported.Values()

			Emit "class "+mdecl.munged+":"
			
			IncTab()
			For Local decl:=Eachin mdecl.Semanted
				If decl.IsExtern() Or decl.scope.ClassScope() Continue
			
				Local gdecl:=GlobalDecl( decl )
				If gdecl
					Emit TransDecl( gdecl )+"="+gdecl.init.Trans()
					Continue
				Endif
				
				Local fdecl:=FuncDecl( decl )
				If fdecl
					EmitFuncDecl fdecl
					Continue
				Endif
			Next
			
			If mdecl=app.mainModule
				
				BeginLocalScope
				Emit ""
				Emit "@staticmethod"
				Emit "def bbInit():"
				IncTab()
				For Local decl:=Eachin app.semantedGlobals
					Emit TransGlobal( decl )+"="+decl.init.Trans()
				Next
				
				Emit "return 0"
				DecTab()
				EndLocalScope
				
			Endif
			
			Emit "pass"
			Emit ""
					
			DecTab()
			
		Next

		Return JoinLines()
	End
	
	Method Emit(t$)
		Local tabs:String = ""
		For Local i:= 0 Until numTabs
			tabs += "~t"
		Next
		
		lines.AddLast tabs+t
		
	End
	
	Method TransBlock$( block:BlockDecl )
		EmitBlock block
	End
	
	'returns unreachable status!
	Method EmitBlock( block:BlockDecl )
	
		Local ll$ = lines.Last()
		
		Local tNumTabs = numTabs
		
		PushEnv block
	
		'Emit "#"+block.ToString()+" "+block.stmts.Count()
		
		If ll.EndsWith(":")
			numTabs = 1
			Local i = 0
			While ll[i] = 9
				numTabs += 1
				i += 1
			Wend

		EndIf
			
		Local func:=FuncDecl( block )
		
		If func
			emitDebugInfo=ENV_CONFIG<>"release"
			If func.attrs & DECL_NODEBUG emitDebugInfo=False

			If emitDebugInfo EmitEnter func.scope.ident+"."+func.ident
			
		Endif
		
		For Local stmt:Stmt=Eachin block.stmts
		
			_errInfo=stmt.errInfo
			
			If unreachable Exit
			
			If emitDebugInfo
				Local rs:=ReturnStmt( stmt )
				If rs
					If rs.expr
						EmitSetErr stmt.errInfo
						Local t_expr:=TransExprNS( rs.expr )
						EmitLeave
						Emit "return "+t_expr
					Else
						EmitLeave
						Emit "return"
					Endif
					unreachable=True
					Continue
				Endif
				EmitSetErr stmt.errInfo
			Endif
			
			Local t$=stmt.Trans()
			If t Emit t

		Next
		
		Local unr=unreachable
		unreachable=False
		
		If func
			If unr

			Else
				If emitDebugInfo EmitLeave
				
				If Not VoidType( func.retType )
					If func.IsCtor()
						Emit "return self"
					Else
						If func.ModuleScope().IsStrict()
							_errInfo=func.errInfo
							Err "Missing return statement."
						Endif
						Emit "return "+TransValue( func.retType,"" )
					Endif
				Else
					Emit "pass"
				EndIf
			Endif
		Endif
		
		PopEnv
		
		If ll.EndsWith(":")
			numTabs = tNumTabs
		EndIf
		
		Return unr
	End
	
	Method DecTab()
		numTabs -= 1
		If numTabs < 0
			numTabs = 0
		EndIf
	End
	
	Method IncTab()
		numTabs += 1
	End
	
	Method ArgInsert:ArgDecl[](args:ArgDecl[], arg:ArgDecl, pos:Int=0)
		Local targs:ArgDecl[] = args.Resize(args.Length+1)
		
		For Local i:= targs.Length-1 Until pos Step -1
			targs[i] = targs[i-1]
		Next
		
		targs[pos] = arg
		
		Return targs
	End
End